import pandas as pd
import warnings
import plotly.express as px
warnings.filterwarnings("ignore")
import statsmodels.api as sm
import matplotlib.pyplot as plt
import seaborn as sns
import numpy as np
Chargement des données¶
pd.set_option('display.max_columns', None)
# Import
train = pd.read_csv("train.csv")
test = pd.read_csv("test.csv")
stores = pd.read_csv("stores.csv")
transactions = pd.read_csv("transactions.csv").sort_values(["store_nbr", "date"])
# Datetime
train["date"] = pd.to_datetime(train.date)
test["date"] = pd.to_datetime(test.date)
transactions["date"] = pd.to_datetime(transactions.date)
# Data types
train.onpromotion = train.onpromotion.astype("float16")
train.sales = train.sales.astype("float32")
stores.cluster = stores.cluster.astype("int8")
train.head()
| id | date | store_nbr | family | sales | onpromotion | |
|---|---|---|---|---|---|---|
| 0 | 0 | 2013-01-01 | 1 | AUTOMOTIVE | 0.0 | 0.0 |
| 1 | 1 | 2013-01-01 | 1 | BABY CARE | 0.0 | 0.0 |
| 2 | 2 | 2013-01-01 | 1 | BEAUTY | 0.0 | 0.0 |
| 3 | 3 | 2013-01-01 | 1 | BEVERAGES | 0.0 | 0.0 |
| 4 | 4 | 2013-01-01 | 1 | BOOKS | 0.0 | 0.0 |
transactions.head()
| date | store_nbr | transactions | |
|---|---|---|---|
| 1 | 2013-01-02 | 1 | 2111 |
| 47 | 2013-01-03 | 1 | 1833 |
| 93 | 2013-01-04 | 1 | 1863 |
| 139 | 2013-01-05 | 1 | 1509 |
| 185 | 2013-01-06 | 1 | 520 |
stores.head()
| store_nbr | city | state | type | cluster | |
|---|---|---|---|---|---|
| 0 | 1 | Quito | Pichincha | D | 13 |
| 1 | 2 | Quito | Pichincha | D | 13 |
| 2 | 3 | Quito | Pichincha | D | 8 |
| 3 | 4 | Quito | Pichincha | D | 9 |
| 4 | 5 | Santo Domingo | Santo Domingo de los Tsachilas | D | 4 |
def describe_data(data, name):
print(f"\nRésumé statistique de {name}:\n")
print(data.describe())
print(f"\nValeurs manquantes dans {name}:\n")
print(data.isnull().sum())
Résumé statistique des datasets¶
Pour mieux comprendre les distributions des variables et détecter d’éventuelles valeurs manquantes, nous analysons les statistiques descriptives pour chaque dataset.
describe_data(train, "Train")
describe_data(test, "Test")
describe_data(stores, "Stores")
describe_data(transactions, "Transactions")
Résumé statistique de Train:
id date store_nbr \
count 3.000888e+06 3000888 3.000888e+06
mean 1.500444e+06 2015-04-24 08:27:04.703088384 2.750000e+01
min 0.000000e+00 2013-01-01 00:00:00 1.000000e+00
25% 7.502218e+05 2014-02-26 18:00:00 1.400000e+01
50% 1.500444e+06 2015-04-24 12:00:00 2.750000e+01
75% 2.250665e+06 2016-06-19 06:00:00 4.100000e+01
max 3.000887e+06 2017-08-15 00:00:00 5.400000e+01
std 8.662819e+05 NaN 1.558579e+01
sales onpromotion
count 3.000888e+06 3000888.0
mean 3.577758e+02 NaN
min 0.000000e+00 0.0
25% 0.000000e+00 0.0
50% 1.100000e+01 0.0
75% 1.958473e+02 0.0
max 1.247170e+05 741.0
std 1.092778e+03 NaN
Valeurs manquantes dans Train:
id 0
date 0
store_nbr 0
family 0
sales 0
onpromotion 0
dtype: int64
Résumé statistique de Test:
id date store_nbr onpromotion
count 2.851200e+04 28512 28512.000000 28512.000000
mean 3.015144e+06 2017-08-23 12:00:00 27.500000 6.965383
min 3.000888e+06 2017-08-16 00:00:00 1.000000 0.000000
25% 3.008016e+06 2017-08-19 18:00:00 14.000000 0.000000
50% 3.015144e+06 2017-08-23 12:00:00 27.500000 0.000000
75% 3.022271e+06 2017-08-27 06:00:00 41.000000 6.000000
max 3.029399e+06 2017-08-31 00:00:00 54.000000 646.000000
std 8.230850e+03 NaN 15.586057 20.683952
Valeurs manquantes dans Test:
id 0
date 0
store_nbr 0
family 0
onpromotion 0
dtype: int64
Résumé statistique de Stores:
store_nbr cluster
count 54.000000 54.000000
mean 27.500000 8.481481
std 15.732133 4.693395
min 1.000000 1.000000
25% 14.250000 4.000000
50% 27.500000 8.500000
75% 40.750000 13.000000
max 54.000000 17.000000
Valeurs manquantes dans Stores:
store_nbr 0
city 0
state 0
type 0
cluster 0
dtype: int64
Résumé statistique de Transactions:
date store_nbr transactions
count 83488 83488.000000 83488.000000
mean 2015-05-20 16:07:40.866232064 26.939237 1694.602158
min 2013-01-01 00:00:00 1.000000 5.000000
25% 2014-03-27 00:00:00 13.000000 1046.000000
50% 2015-06-08 00:00:00 27.000000 1393.000000
75% 2016-07-14 06:00:00 40.000000 2079.000000
max 2017-08-15 00:00:00 54.000000 8359.000000
std NaN 15.608204 963.286644
Valeurs manquantes dans Transactions:
date 0
store_nbr 0
transactions 0
dtype: int64
Nous avons chargé et exploré les données principales. Voici les points importants :
- Données sans valeurs manquantes :
- Tous les fichiers (
train,test,stores,transactions) sont complets.
- Tous les fichiers (
- Distributions clés :
salesetonpromotiondanstrainprésentent des valeurs significativement différentes selon les magasins et périodes.- Les transactions varient fortement en fonction des magasins (
store_nbr).
Prochaines étapes :¶
Nous allons effectuer une analyse exploratoire plus détaillée (EDA) pour mieux comprendre les relations entre les variables (sales, onpromotion, transactions, etc.).
Analyse exploratoire des ventes¶
sales_by_date = train.groupby("date").sales.sum().reset_index()
fig1 = px.line(
sales_by_date,
x="date",
y="sales",
title="Évolution des ventes totales dans le temps",
labels={"date": "Date", "sales": "Ventes totales"},
template="plotly_white",
)
fig1.update_traces(line=dict(width=2))
fig1.show()
# Moyennes hebdomadaires
train["day_of_week"] = train["date"].dt.dayofweek
weekly_sales = train.groupby("day_of_week").sales.mean().reset_index()
fig2 = px.bar(
weekly_sales,
x="day_of_week",
y="sales",
title="Ventes moyennes par jour de la semaine",
labels={"day_of_week": "Jour de la semaine (Lundi=0, Dimanche=6)", "sales": "Ventes moyennes"},
template="plotly_white",
color="sales",
color_continuous_scale="Blues",
)
fig2.show()
# Moyennes mensuelles
train["month"] = train["date"].dt.month
monthly_sales = train.groupby("month").sales.mean().reset_index()
fig3 = px.bar(
monthly_sales,
x="month",
y="sales",
title="Ventes moyennes par mois",
labels={"month": "Mois", "sales": "Ventes moyennes"},
template="plotly_white",
color="sales",
color_continuous_scale="Greens",
)
fig3.show()
Évolution temporelle des ventes¶
Objectif :¶
Analyser les tendances générales des ventes au fil du temps pour détecter :
- Les hausses et baisses saisonnières.
- Les pics et creux éventuels.
Observations :¶
Le graphique montre :
- Une croissance saisonnière régulière dans les ventes.
- Certains pics significatifs (probablement dus à des promotions ou des jours fériés au vue des périodes durant lesquels ils sont observables).
Ventes moyennes par jour de la semaine¶
Objectif :¶
Explorer si certains jours de la semaine sont plus propices aux ventes.
Observations :¶
- Les ventes sont plus élevées le week-end (samedi et dimanche).
- Les jours de semaine ont des ventes relativement uniformes.
Ventes moyennes par mois¶
Objectif :¶
Analyser la distribution des ventes selon les mois de l’année.
Observations :¶
- Décembre affiche les ventes moyennes les plus élevées, ce qui est cohérent avec la saison des fêtes.
- Les mois d’été semblent légèrement plus performants que les mois de printemps surement du aux vacances qui sont plus propicent à la consommations.
L'impact des promotions sur les ventes.¶
sales_with_promotion = train.groupby(train["onpromotion"] > 0).sales.mean().reset_index()
sales_with_promotion.columns = ["OnPromotion", "AverageSales"]
# Visualisation des ventes moyennes selon la présence de promotions
fig_sales_promo = px.bar(
sales_with_promotion,
x="OnPromotion",
y="AverageSales",
title="Ventes moyennes selon la présence de promotions",
labels={"OnPromotion": "Articles en promotion (Oui/Non)", "AverageSales": "Ventes moyennes"},
template="plotly_white",
color="AverageSales",
color_continuous_scale="Blues",
)
fig_sales_promo.show()
Observations :¶
- Les ventes sont significativement plus élevées lorsqu'il y a des promotions.
- Cela montre que les promotions jouent un rôle clé dans la stimulation des ventes.
# Agréger les données par date
daily_data = train.groupby("date")[["sales", "onpromotion"]].sum()
# Calcul de corrélation Spearman après agrégation
daily_corr = daily_data.corr(method="spearman").iloc[0, 1]
print(f"Corrélation Spearman (après agrégation par jour) : {daily_corr:.4f}")
Corrélation Spearman (après agrégation par jour) : 0.7007
Analyse¶
- La corrélation Spearman calculée (0.7007) est positive et forte, montrant que les jours avec plus de promotions sont généralement associés à des ventes plus élevées.
- Cette relation reste cohérente avec les observations précédentes sur les impacts des promotions.
- Les variations quotidiennes dans le nombre d'articles en promotion expliquent en partie les fluctuations des ventes globales.
# Agréger par cluster
cluster_data = train.merge(stores, on="store_nbr", how="left")
cluster_data = cluster_data.groupby("cluster")[["sales", "onpromotion"]].sum()
# Corrélation par cluster
cluster_corr = cluster_data.corr(method="spearman").iloc[0, 1]
print(f"Corrélation Spearman par cluster : {cluster_corr:.4f}")
# Visualisation par cluster
fig_cluster = px.bar(
cluster_data.reset_index(),
x="cluster",
y="sales",
title="Ventes totales par cluster avec promotions",
labels={"cluster": "Cluster", "sales": "Ventes totales"},
template="plotly_white",
color="onpromotion",
)
fig_cluster.show()
Corrélation Spearman par cluster : 0.7966
Observations :¶
- La corrélation Spearman au niveau des clusters (0.7966) est encore plus forte que la corrélation globale par jour.
- Certains clusters montrent des volumes de ventes significativement plus élevés, en particulier les clusters 6, 8 et 14.
Hypothèses possibles :¶
- Les promotions dans ces clusters pourraient cibler des produits spécifiques ou répondre à des besoins locaux (par exemple, promotions sur des produits saisonniers).
- Ces clusters pourraient inclure des magasins plus grands ou mieux établis, ce qui expliquerait des volumes de ventes plus élevés.
Évolution temporelle des transactions¶
global_transactions = transactions.groupby("date").transactions.sum().reset_index()
# Visualisation de l'évolution globale des transactions
fig11 = px.line(
global_transactions,
x="date",
y="transactions",
title="Évolution temporelle globale des transactions",
labels={"date": "Date", "transactions": "Transactions totales"},
template="plotly_white",
)
fig11.show()
# Transactions par magasin
store_transactions = transactions.groupby(["date", "store_nbr"]).transactions.sum().reset_index()
# Visualisation des transactions par magasin (interactif)
fig12 = px.line(
store_transactions,
x="date",
y="transactions",
color="store_nbr",
title="Évolution temporelle des transactions par magasin",
labels={"date": "Date", "transactions": "Transactions", "store_nbr": "Magasin"},
template="plotly_white",
)
fig12.show()
# Transactions par cluster (en supposant que 'cluster' est dans le fichier 'stores')
transactions_with_clusters = transactions.merge(stores, on="store_nbr", how="left")
cluster_transactions = transactions_with_clusters.groupby(["date", "cluster"]).transactions.sum().reset_index()
# Visualisation des transactions par cluster
fig13 = px.line(
cluster_transactions,
x="date",
y="transactions",
color="cluster",
title="Évolution temporelle des transactions par cluster",
labels={"date": "Date", "transactions": "Transactions", "cluster": "Cluster"},
template="plotly_white",
)
fig13.show()
Transaction global¶
Observations détaillées :¶
- Les transactions globales suivent une tendance saisonnière nette, avec des pics récurrents autour de la période de fin d'année.
- Des baisses spécifiques sont observées pendant certaines périodes (par exemple, janvier).
- Ces fluctuations reflètent probablement le comportement d'achat des consommateurs, influencé par des événements externes (fêtes, jours fériés, promotions).
Hypothèses possibles :¶
- Les pics observés sont probablement dus à des périodes de soldes ou de fêtes (Noël, Nouvel An, etc.).
- Les creux correspondent à des périodes de faible activité commerciale, comme après les fêtes ou pendant les vacances scolaires.
Interprétation :¶
Les transactions globales montrent une forte dépendance aux cycles saisonniers.
Transactions par magasin¶
Observations détaillées :¶
- Les magasins montrent des différences importantes dans leurs volumes de transactions, certaines ayant des pics beaucoup plus élevés.
- Ces variations sont probablement dues à des facteurs tels que :
- La taille des magasins.
- Leur localisation géographique.
- Le type de clientèle qu'ils desservent.
Transactions par cluster¶
Observations détaillées :¶
- Les clusters montrent des volumes de transactions distincts, certains étant clairement plus performants (clusters 6, 8 et 14).
- Cependant, certains clusters présentent des volumes de transactions très proches, comme les clusters 17 et 12 ou 9 et 2. Cela suggère qu’un nouveau clustering pourrait être effectué pour regrouper ces clusters très similaire
- Les transactions dans ces clusters présentent également des pics saisonniers marqués.
Corrélation entre ventes et transactions¶
# Fusion des transactions avec les données de ventes
temp = pd.merge(train.groupby(["date", "store_nbr"]).sales.sum().reset_index(), transactions, how = "left")
# Corrélation entre les ventes et les transactions
correlation =temp.corr("spearman").sales.loc["transactions"]
print(f"Corrélation entre les ventes et les transactions : {correlation:.4f}")
# Visualisation des transactions et des ventes globales
fig8 = px.scatter(
temp,
x="transactions",
y="sales",
title="Corrélation entre les ventes et les transactions",
labels={"transactions": "Transactions", "sales": "Ventes"},
opacity=0.7,
template="plotly_white",
)
fig8.show()
Corrélation entre les ventes et les transactions : 0.8175
Résultats :¶
- La corrélation Spearman globale entre les ventes (
sales) et les transactions (transactions) est très forte : 0.8175. - Le nuage de points confirme une relation monotone positive entre les deux variables : plus le nombre de transactions augmente, plus les ventes augmentent.
Observations détaillées :¶
Bien que la relation soit forte, une dispersion croissante est visible pour les transactions élevées. Cela peut indiquer des variations dues à :
- Les types de produits vendus (ex. produits à faible coût mais en grande quantité).
- Les promotions ciblées sur des produits spécifiques
Corrélation entre ventes et transactions par magasin¶
spearman_by_store = (
temp.groupby("store_nbr")[["sales", "transactions"]]
.corr(method="spearman")
.iloc[0::2, 1]
.reset_index()
.rename(columns={"transactions": "spearman_corr"})
)
# Visualisation des corrélations par magasin
fig9 = px.bar(
spearman_by_store,
x="store_nbr",
y="spearman_corr",
title="Corrélation de Spearman entre ventes et transactions par magasin",
labels={"store_nbr": "Numéro du magasin", "spearman_corr": "Corrélation de Spearman"},
template="plotly_white",
color="spearman_corr",
color_continuous_scale="Viridis",
)
fig9.show()
Résultats :¶
- Les corrélations Spearman par magasin varient significativement :
- Certains magasins ont une corrélation élevée (au-dessus de 0.5).
- D'autres montrent une corrélation faible voire négative.
Observations détaillées :¶
- Les magasins avec une faible corrélation pourraient avoir des produits ou des comportements spécifiques (ex. faible panier moyen ou ventes liées à des services non mesurés par les transaction).
Corrélation avec le prix du pétrole¶
# Charger les données du prix du pétrole
oil_data = pd.read_csv("oil.csv")
oil_data["date"] = pd.to_datetime(oil_data["date"])
# Remplacer les zéros par NaN pour éviter des biais
oil_data["dcoilwtico"] = np.where(oil_data["dcoilwtico"] == 0, np.nan, oil_data["dcoilwtico"])
# Interpolation linéaire pour combler les valeurs manquantes
oil_data["dcoilwtico_interpolated"] =oil_data["dcoilwtico"].interpolate(method="linear")
temp = temp.merge(oil_data, on="date", how="left")
# Corrélations par désagrégation
spearman_corr_sales = temp[["sales", "dcoilwtico_interpolated"]].corr(method="spearman").iloc[0, 1]
spearman_corr_transactions = temp[["transactions", "dcoilwtico_interpolated"]].corr(method="spearman").iloc[0, 1]
print(f"Corrélation de Spearman entre ventes et prix du pétrole (désagrégé) : {spearman_corr_sales:.4f}")
print(f"Corrélation de Spearman entre transactions et prix du pétrole (désagrégé) : {spearman_corr_transactions:.4f}")
# Graphiques de dispersion
fig, axes = plt.subplots(1, 2, figsize=(15, 5))
# Dispersion : Transactions vs. Prix du pétrole
temp.plot.scatter(x="dcoilwtico_interpolated", y="transactions", ax=axes[0])
axes[0].set_title(f"Prix du pétrole et Transactions (Spearman: {spearman_corr_transactions:.4f})")
# Dispersion : Ventes vs. Prix du pétrole
temp.plot.scatter(x="dcoilwtico_interpolated", y="sales", ax=axes[1], color="r")
axes[1].set_title(f"Prix du pétrole et Ventes (Spearman: {spearman_corr_sales:.4f})")
plt.tight_layout()
plt.show()
Corrélation de Spearman entre ventes et prix du pétrole (désagrégé) : -0.3176 Corrélation de Spearman entre transactions et prix du pétrole (désagrégé) : 0.0380
Résultats :¶
- La corrélation Spearman entre les ventes (
sales) et le prix du pétrole (dcoilwtico_interpolated) est faible et négative : -0.3176. - La corrélation Spearman entre les transactions et le prix du pétrole est très faible et positive : 0.0380.
Observations détaillées :¶
- Le prix du pétrole semble peu corrélé aux ventes ou aux transactions globales.
- Une faible corrélation négative pour les ventes pourrait indiquer un lien indirect, comme une baisse de la consommation en période de hausse des prix énergétiques.
- La corrélation positive avec les transactions est probablement due à des variations saisonnières communes (ex. hausse du trafic pendant les vacances).
Les nuages de points montrent une grande dispersion, confirmant l'absence de relation linéaire forte entre le prix du pétrole et les variables internes.
Impact des jours fériés sur les ventes¶
# Charger les données des jours fériés
holidays = pd.read_csv("holidays_events.csv")
holidays["date"] = pd.to_datetime(holidays["date"])
# Ajouter une colonne binaire pour indiquer les jours fériés
holidays["is_holiday"] = 1 # Marquer tous les jours dans holidays comme fériés
# Fusionner avec les données de ventes
sales_with_holidays = train.merge(holidays[["date", "is_holiday"]], on="date", how="left")
sales_with_holidays["is_holiday"] = sales_with_holidays["is_holiday"].fillna(0) # Les jours non fériés deviennent 0
# Étape 1 : Moyennes des ventes les jours fériés et normaux
avg_sales_holidays = sales_with_holidays.groupby("is_holiday").sales.mean().reset_index()
print(avg_sales_holidays)
# Étape 2 : Corrélation Spearman entre `is_holiday` et les ventes
holiday_sales_corr = sales_with_holidays[["sales", "is_holiday"]].corr(method="spearman").iloc[0, 1]
print(f"Corrélation Spearman entre les ventes et les jours fériés : {holiday_sales_corr:.4f}")
# Étape 3 : Visualisation des moyennes
import plotly.express as px
fig_holidays = px.bar(
avg_sales_holidays,
x="is_holiday",
y="sales",
title="Ventes moyennes pendant les jours fériés vs jours normaux",
labels={"is_holiday": "Jour férié (1=Oui, 0=Non)", "sales": "Ventes moyennes"},
template="plotly_white",
color="sales",
color_continuous_scale="Reds",
)
fig_holidays.show()
is_holiday sales 0 0.0 352.159180 1 1.0 393.864777 Corrélation Spearman entre les ventes et les jours fériés : 0.0131
Résultats :¶
- Les ventes moyennes pendant les jours fériés sont 393.86 contre 352.15 pour les jours normaux.
- Cela indique une augmentation moyenne des ventes pendant les jours fériés, bien qu'elle reste relativement modeste.
plt.figure(figsize=(15, 6))
# Série temporelle des ventes
plt.plot(sales_with_holidays["date"], sales_with_holidays["sales"], label="Ventes", color="blue", linewidth=1)
# Points pour les jours fériés
holiday_sales = sales_with_holidays[sales_with_holidays["is_holiday"] == 1]
plt.scatter(
holiday_sales["date"],
holiday_sales["sales"],
color="red",
label="Jours fériés",
zorder=5,
s=20,
)
# Personnalisation
plt.title("Série temporelle des ventes avec jours fériés", fontsize=16)
plt.xlabel("Date", fontsize=12)
plt.ylabel("Ventes", fontsize=12)
plt.legend()
plt.grid(visible=True, linestyle="--", alpha=0.6)
plt.tight_layout()
# Affichage
plt.show()
Résultats :¶
- Les points rouges indiquent les jours fériés sur la série temporelle des ventes globales.
- Les jours fériés correspondent parfois à des pics de ventes significatifs, en particulier autour des fêtes de fin d'année (ex. Noël, Nouvel An).
Observations détaillées :¶
- Les pics de ventes pendant les jours fériés sont visibles, mais pas systématiques.
- Certains jours fériés ne génèrent pas de hausse notable des ventes, ce qui suggère des variations selon le type de jour férié ou son emplacement dans la semaine.
Hypothèses possibles :¶
Les jours fériés de fin d'année (comme Noël) ont un effet plus fort sur les ventes, car ils sont associés à des traditions d’achat (cadeaux, repas de fête).
### Ventes moyennes pour promotions et jours fériés combinés
# Ajouter une variable combinée promotion + jour férié
sales_with_holidays["promo_holiday"] = (
(sales_with_holidays["onpromotion"] > 0) & (sales_with_holidays["is_holiday"] == 1)
).astype(int)
# Moyennes des ventes pour chaque combinaison
avg_sales_promo_holiday = sales_with_holidays.groupby("promo_holiday")["sales"].mean().reset_index()
# Visualisation
fig_promo_holiday = px.bar(
avg_sales_promo_holiday,
x="promo_holiday",
y="sales",
title="Ventes moyennes pour promotions et jours fériés combinés",
labels={"promo_holiday": "Promotion et jour férié (1=Oui, 0=Non)", "sales": "Ventes moyennes"},
template="plotly_white",
color="sales",
color_continuous_scale="Tealgrn",
)
fig_promo_holiday.show()
Analyse :¶
- Lorsqu'une promotion coïncide avec un jour férié, les ventes atteignent leur pic. Cette synergie montre que ces deux facteurs combinés amplifient l'engagement des consommateurs.
Ventes moyennes par jour de la semaine et promotions¶
# Assurez-vous que onpromotion est au bon format
sales_with_holidays["onpromotion"] = sales_with_holidays["onpromotion"].astype("float32")
# Ajouter une variable jour de la semaine
sales_with_holidays["weekday"] = sales_with_holidays["date"].dt.weekday
# Moyennes des ventes par jour de la semaine et promotions
avg_sales_weekday_promo = sales_with_holidays.groupby(["weekday", "onpromotion"])["sales"].mean().reset_index()
# Visualisation
fig_weekday_promo = px.bar(
avg_sales_weekday_promo,
x="weekday",
y="sales",
color="onpromotion",
barmode="group",
title="Ventes moyennes par jour de la semaine et promotions",
labels={"weekday": "Jour de la semaine (0=Lundi, 6=Dimanche)", "sales": "Ventes moyennes", "onpromotion": "Promotions"},
template="plotly_white",
)
fig_weekday_promo.show()
Analyse :¶
- Les ventes augmentent chaque jour de la semaine en présence de promotions. Cependant, les week-ends (samedi et dimanche) présentent une demande plus forte, même sans promotion.(barplot2 du notebook)
- Cela peut refléter des comportements d'achat typiques, où les clients profitent de leur temps libre pour faire leurs courses.
Transactions moyennes pendant les jours fériés vs jours normaux¶
transactions["date"] = pd.to_datetime(transactions["date"])
transactions["store_nbr"] = transactions["store_nbr"].astype(int)
# Fusionner avec les ventes
sales_with_transactions = pd.merge(
sales_with_holidays, transactions, on=["date", "store_nbr"], how="left"
)
# Vérifiez la colonne transactions
print(sales_with_transactions.columns)
sales_with_transactions["onpromotion"] = (sales_with_transactions["onpromotion"] > 0).astype(int)
Index(['id', 'date', 'store_nbr', 'family', 'sales', 'onpromotion',
'day_of_week', 'month', 'is_holiday', 'promo_holiday', 'weekday',
'transactions'],
dtype='object')
# Moyenne des transactions avec et sans promotions
avg_transactions_promo = sales_with_transactions.groupby("onpromotion")["transactions"].mean().reset_index()
# Visualisation
fig_transactions_promo = px.bar(
avg_transactions_promo,
x="onpromotion",
y="transactions",
title="Transactions moyennes en fonction des promotions",
labels={"onpromotion": "Promotions (0=Non, 1=Oui)", "transactions": "Transactions moyennes"},
template="plotly_white",
color="transactions",
color_continuous_scale="Blues",
)
fig_transactions_promo.show()
Analyse :¶
Contrairement aux ventes, l'impact des promotions sur les transactions est relativement faible : on observe une légère augmentation de 1700 à 1750. Cela peut indiquer que les promotions augmentent le montant moyen des achats par transaction, mais n'attirent pas nécessairement plus de clients en termes de fréquence des transactions.
Transactions moyennes pendant les jours fériés vs jours normaux¶
# Moyenne des transactions pendant les jours fériés et jours normaux
avg_transactions_holiday = sales_with_transactions.groupby("is_holiday")["transactions"].mean().reset_index()
# Visualisation
fig_transactions_holiday = px.bar(
avg_transactions_holiday,
x="is_holiday",
y="transactions",
title="Transactions moyennes pendant les jours fériés vs jours normaux",
labels={"is_holiday": "Jour férié (0=Non, 1=Oui)", "transactions": "Transactions moyennes"},
template="plotly_white",
color="transactions",
color_continuous_scale="Purples",
)
fig_transactions_holiday.show()
Analyse :¶
L'augmentation des transactions pendant les jours fériés est modérée, passant de 1700 à environ 1750. Cela suggère que les jours fériés augmentent légèrement la fréquentation des magasins, mais pas de manière significative pour les transactions.
Décomposition saisonnière des ventes (Tendance, Saison, Résidus)¶
from statsmodels.tsa.seasonal import seasonal_decompose
# Décomposition des ventes globales
sales_global = sales_with_holidays.groupby("date")["sales"].sum()
# Appliquer la décomposition saisonnière
decomposition = seasonal_decompose(sales_global, model="additive", period=365)
# Visualisation des composantes
fig, axes = plt.subplots(4, 1, figsize=(15, 12), sharex=True)
axes[0].plot(sales_global, label="Ventes réelles", color="blue")
axes[0].set_title("Ventes réelles")
axes[1].plot(decomposition.trend, label="Tendance", color="orange")
axes[1].set_title("Tendance")
axes[2].plot(decomposition.seasonal, label="Saisonnalité", color="green")
axes[2].set_title("Saisonnalité")
axes[3].plot(decomposition.resid, label="Résidus", color="red")
axes[3].set_title("Résidus")
for ax in axes:
ax.legend()
ax.grid(True)
plt.tight_layout()
plt.show()
Analyse :¶
- Tendance : La tendance montre une augmentation globale des ventes, indiquant une croissance soutenue du marché ou une amélioration continue des performances commerciales.
- Saison : Les pics saisonniers se répètent à des intervalles réguliers, probablement liés à des périodes comme Noël, les soldes ou les fêtes locales.
- Résidus : Les résidus indiquent des fluctuations imprévisibles qui ne sont pas capturées par la tendance ou les variations saisonnières. Ces anomalies peuvent être dues à des événements imprévus comme des crises économiques, des promotions exceptionnelles ou des ruptures de stock.
Conclusion Globale et Implications pour la Prédiction des Ventes Globales¶
Résumé des Observations Clés :¶
Saison et Tendance :
- Les ventes globales présentent une forte composante saisonnière, avec des pics réguliers correspondant à des périodes clés (fin d'année, promotions majeures).
- Une tendance croissante est également observée sur plusieurs années, confirmant une dynamique ascendante du marché.
Influence des Variables Exogènes :
- Transactions :
- Une forte corrélation entre les ventes globales et les transactions ((~0.82)) souligne leur rôle comme prédicteur clé.
- Les transactions influencent directement les volumes de vente et capturent l’activité commerciale globale.
- Promotions :
- Les promotions augmentent significativement les ventes, mais leur effet sur les transactions est limité, suggérant qu’elles agissent directement sur les ventes sans toujours modifier le comportement d’achat.
- Jours Fériés :
- Les jours fériés ont un effet positif sur les ventes globales, bien que cet effet soit plus modéré que celui des promotions.
- Les jours fériés influencent surtout les comportements d'achat à travers des dynamiques locales.
- Combinaison Promotions + Jours Fériés :
- Une synergie entre promotions et jours fériés est observée, générant des ventes nettement supérieures aux périodes normales.
- Jour de la Semaine :
- Les ventes varient selon les jours de la semaine, avec des volumes plus élevés le week-end, ce qui reflète les habitudes d’achat des consommateurs.
- Transactions :
Analyse des Clusters :
- Les clusters de magasins montrent des performances distinctes, mais certains clusters présentent des similitudes très fortes, comme observé dans les transactions.
- Cela suggère qu’un reclustering basé sur des caractéristiques supplémentaires (par exemple, via K-means) pourrait être pertinent pour regrouper davantage les clusters proches.
- Une autre approche intéressante serait de prédire les ventes pour chaque cluster séparément, puis de sommer les résultats pour obtenir une prédiction globale. Cela permettrait de capturer des dynamiques locales et spécifiques à chaque cluster.
Influence Nulle ou Faible :
- Les corrélations faibles avec le prix du pétrole ((~-0.31)) suggèrent que cette variable n’est pas pertinente pour modéliser les ventes globales.
Implications pour la Modélisation :¶
Modèles Adaptés aux Séries Temporelles :
- Les caractéristiques temporelles des données justifient l’utilisation de modèles capables de capturer la saisonnalité, les tendances et les cycles, tels que :
- SARIMAX : Idéal pour intégrer les variables exogènes (transactions, promotions, jours fériés) tout en capturant les composantes temporelles.
- Prophet avec Régressions Exogènes : Excellente alternative pour modéliser des tendances complexes et des événements spécifiques.
- Les caractéristiques temporelles des données justifient l’utilisation de modèles capables de capturer la saisonnalité, les tendances et les cycles, tels que :
Utilisation des Variables Exogènes :
- Les variables exogènes pertinentes incluent :
- Transactions : Corrélation forte avec les ventes.
- Promotions et Jours Fériés : Impact direct et combiné sur les ventes.
- Jour de la Semaine : Variabilité intra-semaine importante.
- Leur intégration est essentielle pour expliquer les variations des ventes globales.
- Les variables exogènes pertinentes incluent :
Approches Non Linéaires :
- Les relations complexes entre promotions, jours fériés, et ventes justifient des modèles non linéaires tels que :
- XGBoost ou Random Forest : Pour capturer les interactions non linéaires entre les variables.
- LSTM (Long Short-Term Memory) : Idéal pour les dépendances à long terme dans les séries temporelles, en intégrant les variables exogènes.
- Les relations complexes entre promotions, jours fériés, et ventes justifient des modèles non linéaires tels que :
Validation et Robustesse :
- Validation Croisée Temporelle : Indispensable pour évaluer les modèles sur des fenêtres temporelles distinctes et éviter les fuites.
- Analyse des Résidus : Identifier les structures non expliquées par les modèles pour améliorer leur robustesse.
Clusterisation et Approches Multi-Niveaux :
- Les analyses exploratoires des clusters suggèrent de tester des modèles spécifiques à chaque cluster, avec une sommation des prédictions pour obtenir une vision globale.
- L’utilisation de techniques comme K-means pour recluster les magasins ou groupes similaires peut renforcer la précision des modèles en regroupant des magasins aux dynamiques comparables.
Conclusion : Les analyses exploratoires montrent que les variables exogènes comme les transactions, promotions, jours fériés et jour de la semaine sont des prédicteurs robustes des ventes globales. Un modèle temporel intégrant ces variables (comme SARIMAX ou Prophet avec régressions) est bien adapté pour la prédiction. De plus, l’analyse des clusters met en évidence des différences et similarités entre groupes de magasins, justifiant à la fois des reclusterisations (via K-means) et des approches de modélisation spécifiques par cluster. Ces méthodes combinées garantiront des prédictions fiables et exploitables.